import java.io.*;
import java.util.ArrayList;
import java.util.Scanner;

/** {@code Test}类 测试用主函数 */
public class Test {
    public static final Scanner keyin = new Scanner(System.in);
    /** 玩家数量 */
    public static final int PLAYER_NUM = 4;
    /** 剩余玩家数量(未出局) */
    static int numberOfEffectivePlayers = PLAYER_NUM;
    /** 剩余玩家数量(未出局且未弃牌) */
    static int numberOfTruePlayers;
    /** 一整局游戏的轮数 */
    static final int GAME_NUM = PLAYER_NUM * 2;
    /** 庄家偏移量 */
    static int offset = 0;
    /** 回合开始时的场上最大押注额 */
    static int initialBet;

    /** 分隔符 */
    static final String[] SEPARATOR = {
            "-----------------------------------------------------------------------------------------------",
            "===============================================================================================",
            "########################################## ROUND END ##########################################" };

    /** Test类不可以构造 */
    private Test() {
        throw new IllegalStateException("Utility class");
    }

    /** 本回合未结束 */
    public static boolean isRoundNotEnd() {
        return Game.getCurrentRoundBets() != initialBet;
    }

    public static void ready() {
        // 初始化玩家链表
        Game.setNumberOfPlayers(PLAYER_NUM);
        Game.initializePlayers();
    }

    // public static void main(String[] args) {
    /** 开始一局游戏 */
    public static void start() {
        Cards testCards = new Cards();
        for (int i = 0; i < GAME_NUM; i++) {
            Game.currentPlayer().connect();
            Game.toNextPlayer();
        }
        // 一局游戏
        for (int n = 1; n <= GAME_NUM; n++) {
            numberOfTruePlayers = numberOfEffectivePlayers;// 剩余玩家数量(未出局且未弃牌)
            String gameInfo = "第" + n + "轮游戏, 剩余玩家数量: " + numberOfTruePlayers;
            Server.log.info(gameInfo);

            play(testCards);// 进行一轮游戏
            offsetIncreasing();// 一轮游戏后 庄家向后顺位一个玩家
            recalculateNumberOfEffectivePlayers();// 重新计算未出局人数
            System.out.print("结束时玩家资金:");
            printAllPlayerMoney();// 打印玩家资金
            System.out.println(SEPARATOR[2]);
            if (numberOfEffectivePlayers == 1)// 剩余未出局的玩家只剩一人 直接获胜
                break;
        }
        // 寻找获胜所需的钱
        Game.setIndexOfPlayers(0);
        int maxMoney = Game.currentPlayer().getMoney();
        for (int i = 1; i < PLAYER_NUM; i++) {
            Game.toNextPlayer();
            if (Game.currentPlayer().getMoney() > maxMoney) {
                maxMoney = Game.currentPlayer().getMoney();
            }
        }
        // 寻找获胜的玩家
        ArrayList<Player> winnerList = new ArrayList<>();
        for (int i = 0; i < PLAYER_NUM; i++) {
            Game.toNextPlayer();
            if (Game.currentPlayer().getMoney() == maxMoney) {
                winnerList.add(Game.currentPlayer());
            }
        }
        // 告知游戏结束
        for (int i = 0; i < PLAYER_NUM; i++) {
            try {
                Player p = Game.currentPlayer();
                p.getOut().println("<game over>");
                p.getOut().flush();
                if (!p.getIn().readLine().equals("received")) {
                    throw new IOException("客户端未响应");
                }
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
            Game.toNextPlayer();
        }
        // 打印获胜玩家 可能存在多个并列获胜玩家
        System.out.print("WINNER: ");
        int size = winnerList.size();
        for (int i = 0; i < size; i++) {
            System.out.print(winnerList.get(i).getUserName() + ' ');
        }
        System.out.println();
    }

    /** 开启一轮游戏 */
    public static void play(Cards cards) {
        // 回到第一个人 初始化
        Game.setIndexOfPlayers(0);
        for (int i = 0; i < PLAYER_NUM; i++) {
            Game.currentPlayer().setIs弃牌(false);
            Game.currentPlayer().setIs庄家(false);
            Game.toNextPlayer();
        }
        // 转向庄家
        for (int i = 0; i < offset; i++) {
            Game.toNextTruePlayer();
        }
        Game.currentPlayer().setIs庄家(true);
        blinds();// 令庄家的后两名玩家下盲注
        System.out.print("开始时玩家资金:");
        printAllPlayerMoney();// 打印玩家资金
        // 转向庄家
        for (int i = 0; i < offset; i++) {
            Game.toNextTruePlayer();
        }
        System.out.println("本轮庄家:\t" + Game.currentPlayer().toString());
        // 总回合开始 发公共牌
        for (int i = 0; i < 5; i++)
            Game.publicCard[i] = cards.getCard();
        // <Debug>
        System.out.println("本轮公共牌:\t" + Game.publicCard[0] + "\t" + Game.publicCard[1] + "\t" + Game.publicCard[2]
                + "\t" + Game.publicCard[3] + "\t" + Game.publicCard[4]);
        try {
            // 第一回合
            System.out.println(SEPARATOR[1]);
            System.out.println("第一回合 玩家摸牌回合 - 玩家从牌堆摸取两张起脚牌");
            for (int i = 0; i < PLAYER_NUM; i++) {
                Game.currentPlayer().turn1(cards);
                Game.toNextPlayer();
            }
            // 第二回合
            System.out.println(SEPARATOR[1]);
            System.out.println("第二回合 底牌押注回合 - 公共牌出现以前的第一轮押注圈");
            // 运行押注
            round();
            // 第三回合
            System.out.println(SEPARATOR[1]);
            System.out.println("第三回合 翻牌押注回合 - 首三张公共牌出现以后的押注圈");
            printCard(3);
            // 运行押注
            round();
            // 第四回合
            System.out.println(SEPARATOR[1]);
            System.out.println("第四回合 转牌押注回合 - 第四张公共牌出现以后的押注圈");
            printCard(4);
            // 运行押注
            round();
            // 第五回合
            System.out.println(SEPARATOR[1]);
            System.out.println("第五回合 河牌押注回合 - 第五张公共牌出现以后的押注圈");
            printCard(5);
            // 运行押注
            round();
            // 清除已压注额
            for (int i = 0; i < PLAYER_NUM; i++) {
                Game.currentPlayer().setPlayerBets(0);
                Game.toNextPlayer();
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();

        }
        // 结算奖励回合
        System.out.println(SEPARATOR[1]);
        System.out.println("最终回合 结算奖励回合 - 清空奖池并结算玩家奖励");
        Game.toNextTruePlayer();// 寻找有效玩家
        // 寻找赢家 在未出局且未弃牌的玩家中寻找
        Player winner = Game.currentPlayer();
        for (int i = 1; i < numberOfTruePlayers; i++) {
            Game.toNextTruePlayer();
            if (Game.currentPlayer().getScore() > winner.getScore())
                winner = Game.currentPlayer();
        }
        System.out.println(winner.toString() + "\tWIN! +$" + Game.getPrizePoolAmount());
        // 告知其他玩家的牌及牌型
        for (int i = 0; i < PLAYER_NUM; i++) {
            try {
                Game.currentPlayer().getOut().println("<publish>");
                Game.currentPlayer().getOut().flush();
                if (!Game.currentPlayer().getIn().readLine().equals("received")) {
                    throw new IOException(Game.currentPlayer().getSocket() + "客户端未响应");
                }
                Player p = Game.currentPlayer();
                String[] t = new String[] { "散牌", "对子", "两对", "三条", "顺子", "同花", "葫芦", "四条", "同花顺" };
                for (int j = 0; j < PLAYER_NUM; j++) {
                    p.getOut().println(Game.currentPlayer().getPrivateCard()[0].getValue());
                    p.getOut().flush();
                    if (!p.getIn().readLine().equals("received")) {
                        throw new IOException("客户端未响应");
                    }
                    p.getOut().println(Game.currentPlayer().getPrivateCard()[1].getValue());
                    p.getOut().flush();
                    if (!p.getIn().readLine().equals("received")) {
                        throw new IOException("客户端未响应");
                    }
                    p.getOut().println(t[(int) (Game.currentPlayer().getScore() >> 56) - 1]);
                    p.getOut().flush();
                    if (!p.getIn().readLine().equals("received")) {
                        throw new IOException("客户端未响应");
                    }
                    Game.toNextPlayer();
                }
                Game.currentPlayer().getOut()
                        .println("玩家[" + winner.getUserName() + "]获胜! +$" + Game.getPrizePoolAmount());
                Game.currentPlayer().getOut().flush();
                if (!Game.currentPlayer().getIn().readLine().equals("received")) {
                    throw new IOException(Game.currentPlayer().getSocket() + "客户端未响应");
                }
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
            Game.toNextPlayer();
        }
        // 结算奖励 清空奖池
        winner.changeMoney(Game.getPrizePoolAmount());
        Game.setPrizePoolAmount(0);// 清零奖池
        // updateAllInfo
        try {
            for (int i = 0; i < PLAYER_NUM; i++) {
                Player p = Game.currentPlayer();
                updateAllInfo(p);
                // 告知当前回合操作玩家
                p.getOut().println("本轮结束");
                p.getOut().flush();
                if (!p.getIn().readLine().equals("received")) {
                    throw new IOException("客户端未响应");
                }
                Game.toNextPlayer();
            }
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
        // 告知回合结束
        for (int i = 0; i < PLAYER_NUM; i++) {
            Game.currentPlayer().getOut().println("<round end>");
            Game.currentPlayer().getOut().flush();
            try {
                if (!Game.currentPlayer().getIn().readLine().equals("received")) {
                    throw new IOException(Game.currentPlayer().getSocket() + "客户端未响应");
                }
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
            Game.toNextPlayer();
        }
        // 等待玩家继续
        for (int i = 0; i < PLAYER_NUM; i++, Game.toNextPlayer()) {
            if (Game.currentPlayer().getMoney() == 0) {
                continue;
            }
            try {
                if (!Game.currentPlayer().getIn().readLine().equals("<continue>")) {
                    throw new IOException("输入不是<continue>");
                }
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }
        // 通知收到
        for (int i = 0; i < PLAYER_NUM; i++) {
            Game.currentPlayer().getOut().println("<new game>");
            Game.currentPlayer().getOut().flush();
            try {
                if (!Game.currentPlayer().getIn().readLine().equals("received")) {
                    throw new IOException(Game.currentPlayer().getSocket() + "客户端未响应");
                }
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
            Game.toNextPlayer();
        }
        // 总回合结束 洗牌
        cards.reset();
    }

    /** 进行一个押注回合 */
    private static void round() throws IOException {
        int index = 0;
        do {
            initialBet = Game.getCurrentRoundBets();
            for (; index < PLAYER_NUM && numberOfTruePlayers > 1; index++, Game.toNextPlayer()) {
                // 如果未弃牌的玩家仅剩一人 直接跳出回合
                Player currentPlayer = Game.currentPlayer();
                System.out.println(SEPARATOR[0]);
                System.out.print("回合开始: ");
                printPlayerInfo(currentPlayer);

                // updateAllInfo
                for (int i = 0; i < PLAYER_NUM; i++) {
                    Player p = Game.currentPlayer();
                    updateAllInfo(p);
                    // 告知当前回合操作玩家
                    p.getOut().println("当前玩家");
                    p.getOut().flush();
                    if (!p.getIn().readLine().equals("received")) {
                        throw new IOException("客户端未响应");
                    }
                    p.getOut().println("当前玩家: " + currentPlayer.getUserName());
                    p.getOut().flush();
                    if (!p.getIn().readLine().equals("received")) {
                        throw new IOException("客户端未响应");
                    }
                    Game.toNextPlayer();
                }

                if (currentPlayer.getIs弃牌() || currentPlayer.getIs出局()) {
                    System.out.println("该玩家已经弃牌/出局.");
                } else if (currentPlayer.getPlayerBets() == Integer.MAX_VALUE) {
                    System.out.println("该玩家已经全押.");
                } else {
                    currentPlayer.turn();
                    System.out.print("回合结束: ");
                    printPlayerInfo(currentPlayer);
                    if (currentPlayer.getIs弃牌()) {// 记录弃牌 一个玩家一轮最多只会执行一次
                        numberOfTruePlayers--;
                    }
                    if (Game.getCurrentRoundBets() != initialBet) {// 若玩家加注 以他为中心 再次开始本回合
                        Game.toNextPlayer();
                        break;
                    }
                }
            }
            index = (Game.getCurrentRoundBets() != initialBet) ? 1 : 0;
        } while (isRoundNotEnd());
        resetAllPlayerBets();// 重置该回合所有玩家投注额 为下回合做准备
        Game.setCurrentRoundBets(0);// 重置该回合应投注额 为下回合做准备
        // 回到庄家
        Game.setIndexOfPlayers(0);
        for (int i = 0; i < offset; i++)
            Game.toNextTruePlayer();
    }

    /** 重置所有玩家的上回合投注额 */
    private static void resetAllPlayerBets() {
        for (int i = 0; i < PLAYER_NUM; i++) {
            if (Game.currentPlayer().getPlayerBets() != Integer.MAX_VALUE)// 若全押则不清零
                Game.currentPlayer().setPlayerBets(0);
            Game.toNextPlayer();
        }
    }

    /** 庄家更替 向后一位玩家 */
    private static void offsetIncreasing() {
        offset++;
        offset %= PLAYER_NUM;
    }

    /** 重新计算未出局人数 */
    private static void recalculateNumberOfEffectivePlayers() {
        numberOfEffectivePlayers = PLAYER_NUM;
        for (int i = 0; i < PLAYER_NUM; i++) {
            Game.currentPlayer().setIs出局(Game.currentPlayer().getMoney() == 0);
            if (Game.currentPlayer().getIs出局()) {
                numberOfEffectivePlayers--;
            }
            Game.toNextPlayer();
        }
        Game.setIndexOfPlayers(0);
        System.out.println("[本轮游戏结束后还剩" + numberOfEffectivePlayers + "人.]");
    }

    /** 打印所有玩家金钱 */
    private static void printAllPlayerMoney() {
        for (int i = 0; i < PLAYER_NUM; i++) {
            System.out.print('\t' + Game.currentPlayer().toString() + ": $" + Game.currentPlayer().getMoney());
            Game.toNextPlayer();
        }
        System.out.println();
    }

    private static void printPlayerInfo(Player p) {
        System.out.println(p.toString() + ": 已投注:" + p.getPlayerBets() + ". 资金: " + p.getMoney() + ". 应投注额: "
                + Game.getCurrentRoundBets() + ". 奖池: " + Game.getPrizePoolAmount());
    }

    /** 令庄家的后两名玩家下盲注 */
    private static void blinds() {
        // 小盲注
        Game.toNextTruePlayer();
        if (Game.currentPlayer().getMoney() >= 2) {
            Game.currentPlayer().changeMoney(-2);
            Game.currentPlayer().changePlayerBets(2);
            Game.changePrizePoolAmount(2);
        } else {// 全押
            Game.changePrizePoolAmount(Game.currentPlayer().getMoney());
            Game.currentPlayer().setMoney(0);
            Game.currentPlayer().changePlayerBets(Integer.MAX_VALUE);
        }
        // 大盲注
        Game.toNextTruePlayer();
        if (Game.currentPlayer().getMoney() >= 4) {
            Game.currentPlayer().changeMoney(-4);
            Game.currentPlayer().changePlayerBets(4);
            Game.changePrizePoolAmount(4);
        } else {// 全押
            Game.changePrizePoolAmount(Game.currentPlayer().getMoney());
            Game.currentPlayer().setMoney(0);
            Game.currentPlayer().changePlayerBets(Integer.MAX_VALUE);
        }
        // 大小盲注给奖池和本轮应押注数带来的影响
        Game.setCurrentRoundBets(4);
        // 回到第一个人
        Game.setIndexOfPlayers(0);
    }

    public static void printCard(int n) throws IOException {
        for (int i = 0; i < PLAYER_NUM; i++) {
            Game.currentPlayer().getOut().println("<public>");
            Game.currentPlayer().getOut().flush();
            if (!Game.currentPlayer().getIn().readLine().equals("received")) {
                throw new IOException("客户端未响应");
            }
            for (int j = 0; j < n; j++) {
                Game.currentPlayer().getOut().println(Game.publicCard[j].getValue());
                Game.currentPlayer().getOut().flush();
                if (!Game.currentPlayer().getIn().readLine().equals("received")) {
                    throw new IOException("客户端未响应");
                }
            }
            for (int j = 0; j < 5 - n; j++) {
                Game.currentPlayer().getOut().println("00");
                Game.currentPlayer().getOut().flush();
                if (!Game.currentPlayer().getIn().readLine().equals("received")) {
                    throw new IOException("客户端未响应");
                }
            }
            Game.toNextPlayer();
        }
        // 回到庄家
        Game.setIndexOfPlayers(0);
        for (int i = 0; i < offset; i++)
            Game.toNextTruePlayer();
    }

    private static void updateAllInfo(Player p) throws IOException {
        p.getOut().println("<update>");
        p.getOut().flush();
        if (!p.getIn().readLine().equals("received")) {
            throw new IOException(p.getSocket() + "客户端未响应");
        }
        // 告知玩家金额
        p.getOut().println("玩家金额");
        p.getOut().flush();
        if (!p.getIn().readLine().equals("received")) {
            throw new IOException(p.getSocket() + "客户端未响应");
        }
        for (int i = 0; i < PLAYER_NUM; i++) {
            p.getOut().println(Game.currentPlayer().getMoney());
            p.getOut().flush();
            if (!p.getIn().readLine().equals("received")) {
                String playerSocket = p.getSocket().toString();
                throw new IOException(playerSocket + "客户端未响应");
            }
            Game.toNextPlayer();
        }
        // 告知玩家应压注额
        p.getOut().println("应压注额");
        p.getOut().flush();
        if (!p.getIn().readLine().equals("received")) {
            throw new IOException(p.getSocket() + "客户端未响应");
        }
        p.getOut().println(Game.getCurrentRoundBets());
        p.getOut().flush();
        if (!p.getIn().readLine().equals("received")) {
            throw new IOException(p.getSocket() + "客户端未响应");
        }
        // 告知玩家已压注额
        p.getOut().println("已压注额");
        p.getOut().flush();
        if (!p.getIn().readLine().equals("received")) {
            throw new IOException(p.getSocket() + "客户端未响应");
        }
        p.getOut().println(p.getPlayerBets());
        p.getOut().flush();
        if (!p.getIn().readLine().equals("received")) {
            throw new IOException(p.getSocket() + "客户端未响应");
        }
        // 告知玩家奖池金额
        p.getOut().println("奖池金额");
        p.getOut().flush();
        if (!p.getIn().readLine().equals("received")) {
            throw new IOException(p.getSocket() + "客户端未响应");
        }
        p.getOut().println(Game.getPrizePoolAmount());
        p.getOut().flush();
        if (!p.getIn().readLine().equals("received")) {
            throw new IOException(p.getSocket() + "客户端未响应");
        }
    }
}
